﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class DataContainer<T> : ReadOnlyable
	where T : AbstractDataComponent, new()
{
	private List<string> groupIds = new List<string>(); //A list of groupIds that the entity should be associated with.
	private Dictionary<System.Type,T> components = new Dictionary<System.Type,T>();

	public DataContainer ()
	{
	}
	
	protected override ReadOnlyable GetNewInstance ()
	{
		return new DataContainer<T>();
	}
	
	public override void CopyTo (ReadOnlyable target)
	{
		if (!(target is DataContainer<T>))
		{
			Debug.LogError("Target must be of type '" + typeof(DataContainer<T>) + "'.");
			return;
		}

		//TODO: Allow the new DataContainer instance to hold old data component instances and only clone them in the getter when they are retrieved. May really help performance in the end.
		DataContainer<T> targetDataContainer = (DataContainer<T>)target;
		foreach (KeyValuePair<System.Type,T> pair in components)
		{
			T data = pair.Value;
			T targetData = new T();
			data.CopyTo(targetData);
			targetDataContainer.AddData(targetData);
		}

		for (int i = 0 ; i < groupIds.Count ; i++)
		{
			string groupId = groupIds[i];
			targetDataContainer.AddGroupId(groupId);
		}
	}

	public void AddGroupId (string groupId)
	{
		if (ReadOnly)
		{
			LogErrorWriteToReadOnly();
			return;
		}

		if (HasGroupId(groupId))
		{
			return;
		}

		groupIds.Add(groupId);
	}

	public void RemoveGroupId (string groupId)
	{
		if (ReadOnly)
		{
			LogErrorWriteToReadOnly();
			return;
		}

		if (!HasGroupId(groupId))
		{
			return;
		}

		groupIds.Remove(groupId);
	}

	public void RemoveAllGroupIds ()
	{
		if (ReadOnly)
		{
			LogErrorWriteToReadOnly();
			return;
		}

		groupIds = new List<string>();
	}

	public bool HasGroupId (string groupId)
	{
		return groupIds.Contains(groupId);
	}

	public int GroupIdsCount ()
	{
		return groupIds.Count;
	}

	public string GetGroupIdAt (int index)
	{
		if (index < 0 || index >= groupIds.Count)
		{
			Debug.LogError("Index value '" + index + "' out of '" + groupIds.Count + "' is invalid.");
			return null;
		}

		return groupIds[index];
	}

	public string[] CopyGroupIds ()
	{
		string[] groupIdsCopy = new string[groupIds.Count];
		groupIds.CopyTo(groupIdsCopy);
		return groupIdsCopy;
	}
	
	public T2 AddData<T2> ()
		where T2 : T, new() //Specifies that T has a public, parameterless constructor.
	{
		if (ReadOnly)
		{
			LogErrorWriteToReadOnly();
			return default(T2);
		}
		
		System.Type type = typeof(T2);
		if (HasData(type))
		{
			Debug.LogError("Already has a data component for type '" + type + "'.");
			return default(T2);
		}
		
		T2 data = new T2();
		components.Add(type , data);
		return data;
	}
	
	public void AddData (T data)
	{
		if (ReadOnly)
		{
			LogErrorWriteToReadOnly();
			return;
		}
		
		System.Type type = data.GetType();
		if (HasData(type))
		{
			Debug.LogError("Already has a data component for type '" + type + "'.");
			return;
		}

		components.Add(type , data);
	}
	
	public void RemoveData<T2> ()
		where T2 : T
	{
		System.Type type = typeof(T2);
		
		if (ReadOnly)
		{
			LogErrorWriteToReadOnly();
			return;
		}
		
		if (!HasData(type))
		{
			Debug.LogError("No data component of type '" + type + "'.");
			return;
		}
		
		components.Remove(type);
	}
	
	public bool HasData<T2> ()
		where T2 : T
	{
		System.Type type = typeof(T2);
		return HasData(type);
	}
	
	public bool HasData (System.Type type)
	{
		return components.ContainsKey(type);
	}
	
	public T2 GetData<T2> ()
		where T2 : T
	{
		System.Type type = typeof(T2);
		if (!components.ContainsKey(type))
		{
			return null;
		}
		
		return (T2)components[type];
	}
}
